package server;

import java.io.Serializable;
import java.util.logging.Logger;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import javax.ejb.MessageDriven;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.MessageProducer;
import javax.jms.ObjectMessage;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.Topic;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import dst.ass3.dto.ProcessTaskWrapperDTO;
import dst.ass3.dto.RateTaskWrapperDTO;
import dst.ass3.dto.TaskWrapperDTO;
import dst.ass3.jms.Constants;
import dst.ass3.model.Complexity;
import dst.ass3.model.ITaskWrapper;
import dst.ass3.model.LifecycleState;
import dst.ass3.model.TaskWrapper;

@MessageDriven(mappedName = Constants.SERVER_QUEUE)
public class Server implements MessageListener {

	private final static Logger LOGGER = Logger.getLogger(Server.class.getName());
	
	//defines Connection_Factory
    @Resource(mappedName = Constants.CONNECTION_FACTORY)
    private ConnectionFactory connectionFactory;
    
    private Connection connection = null;
    private Session session = null;

    //defines the queue for the taskforces: Point-to-Point
    @Resource(mappedName = Constants.TASKFORCES_QUEUE)
    private Queue taskforcesQueue;
    private MessageProducer taskforcesProducer = null;

    //defines the queue for the scheduler: Point-to-Point
    @Resource(mappedName = Constants.SCHEDULER_QUEUE)
    private Queue schedulerQueue;
    private MessageProducer schedulerProducer = null;

    //defines the topic for the taskworker: Point-to-Multipoint, Publish-and-Subscribe
    @Resource(mappedName = Constants.TASKWORKER_TOPIC)
    private Topic taskworkerTopic;
    private MessageProducer taskworkerProducer = null;

    @PersistenceContext
    private EntityManager entityManager;
    
    @PostConstruct
    public void start() {
        try {
        	//create a connection from the factory
        	//http://docs.oracle.com/javaee/1.4/api/javax/jms/Connection.html
            connection = connectionFactory.createConnection();
            
            //session to create the Producers
            session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
            
            //Producers are needed to write to the queue/topic
            taskforcesProducer = session.createProducer(taskforcesQueue);
            schedulerProducer = session.createProducer(schedulerQueue);
            taskworkerProducer = session.createProducer(taskworkerTopic);

            //Starts (or restarts) a connection's delivery of incoming messages. 
            connection.start();
            
        } catch (JMSException e) {
            e.printStackTrace();
        }
        LOGGER.info("started");
    }

    
    /**
     * close all the 
     */
    @PreDestroy
    public void stop() {
		try {
			if (taskforcesProducer != null)
				taskforcesProducer.close();
		} catch (JMSException e) {
			e.printStackTrace();
		}
		
		try {
			if (schedulerProducer != null)
				schedulerProducer.close();
		} catch (JMSException e) {
			e.printStackTrace();
		}
		
		try {
			if (taskworkerProducer != null)
				taskworkerProducer.close();
		} catch (JMSException e) {
			e.printStackTrace();
		}
		
		try {
			if (session != null)
				session.close();
		} catch (JMSException e) {
			e.printStackTrace();
		}
		
		try {
			if (connection != null)
				connection.close();
		} catch (JMSException e) {
			e.printStackTrace();
		}
		
		LOGGER.info("closed");
    }

    
    
    /**
     * Receives the messages from the Server-Queue and calls the methods depending on the type
     */
    @Override
    public void onMessage(Message msg) {
        ObjectMessage m = (ObjectMessage)msg;
        if (m == null) {
        	LOGGER.info("Message received is null");
            return;
        }
        else{
	        try {
	            int receivedType = m.getIntProperty(Constants.PROPERTY_MESSAGE_TYPE);
	            
	            LOGGER.info("Message type: "+receivedType+" and object "+ m.getObject());
	            
	            switch (receivedType) {
		            case Constants.MSG_SCHEDULER_TO_SERVER_CREATE:
		            	schedulerCreate(m.getObject()); break;
		            case Constants.MSG_SCHEDULER_TO_SERVER_INFO:
		            	schedulerInfo(m.getObject()); break;
		            case Constants.MSG_TASKFORCE_TO_SERVER_DECISION:
		            	taskforceDecision(m.getObject()); break;
		            case Constants.MSG_TASKWORKER_TO_SERVER_PROCESSED:
		            	taskworkerProcessed(m.getObject()); break;
		            default:
		            	LOGGER.info("Type received not known: "+receivedType);
	            }
	        } catch (JMSException e) {
	            e.printStackTrace();
	        }
        }
    }

    
    private void taskworkerProcessed(Serializable object) {
    	if (object == null) {
        	LOGGER.info("No object received");
            return;
        }
    	else{
	        ProcessTaskWrapperDTO dto = (ProcessTaskWrapperDTO)object;
	        
	        try {
	            ITaskWrapper task = getTaskWrapper(dto.getId());  //receive it from DB
	
	            task.setState(dto.getState());	//set State
	
	            entityManager.persist(task);		//persist it
	
	            //create new TaskWrapperObject and send it to Scheduler as response
	            TaskWrapperDTO schedDTO = new TaskWrapperDTO(task);
	            ObjectMessage sm = session.createObjectMessage(schedDTO);
	            sm.setIntProperty(Constants.PROPERTY_MESSAGE_TYPE, Constants.MSG_SERVER_TO_SCHEDULER_PROCESSED);
	            
	            schedulerProducer.send(sm);		//Send Scheduler that everything is processed
	        } catch (JMSException e) {
	            e.printStackTrace();
	        } catch (Exception e) {
	            e.printStackTrace(); // eventually of getSingleResult() 
	        }
    	}
    }

    private void taskforceDecision(Serializable object) {
    	if (object == null) {
    		LOGGER.info("No object received");
            return;
        }
    	else{
	    	RateTaskWrapperDTO dto = (RateTaskWrapperDTO)object; 
	
	        try {
	            final ITaskWrapper task = getTaskWrapper(dto.getId()); //receive it from DB
	
	            //set States
	            task.setComplexity(dto.getComplexity());
	            task.setState(dto.getState());
	            task.setRatedBy(dto.getRatedBy());
	
	            //persists to db
	            entityManager.persist(task);
	
	            if (dto.getState() == LifecycleState.READY_FOR_PROCESSING) {		//OK The Process then send TaskWorker
	                //send response to taskworkers
	                ProcessTaskWrapperDTO ptDTO = new ProcessTaskWrapperDTO(task);
	                ObjectMessage sm = session.createObjectMessage(ptDTO);
	                
	                sm.setIntProperty(Constants.PROPERTY_MESSAGE_TYPE, Constants.MSG_SERVER_TO_TASKWORKE_ASSIGN);
	                sm.setStringProperty(Constants.PROPERTY_TASKFORCE, dto.getRatedBy());
	                sm.setStringProperty(Constants.PROPERTY_COMPLEXITY, dto.getComplexity().toString());
	                
	                taskworkerProducer.send(sm);
	            } else {  //if it is not reade for processing send response to scheduler
	                TaskWrapperDTO schedDTO = new TaskWrapperDTO(task);
	                ObjectMessage sm = session.createObjectMessage(schedDTO);
	                sm.setIntProperty(Constants.PROPERTY_MESSAGE_TYPE, Constants.MSG_SERVER_TO_SCHEDULER_DENIED);
	                
	                schedulerProducer.send(sm);
	            }
	        } catch (JMSException e) {
	            e.printStackTrace();
	        } catch (Exception e) {
	            e.printStackTrace(); //eventually because of getSingleResult()
	        }
    	}
    }

    private void schedulerInfo(Serializable object) {
    	if (object == null) {
    		LOGGER.info("No object received");
            return;
        }
    	else{
	    	TaskWrapperDTO dto = (TaskWrapperDTO)object;
	        
	        ITaskWrapper task = getTaskWrapper(dto.getId());
	
	        try {
	            // Reply to scheduler
	
	            TaskWrapperDTO schedDTO = new TaskWrapperDTO(task);
	            ObjectMessage sm = session.createObjectMessage(schedDTO);
	            sm.setIntProperty(Constants.PROPERTY_MESSAGE_TYPE, Constants.MSG_SERVER_TO_SCHEDULER_INFO);
	            
	            schedulerProducer.send(sm);
	        } catch (JMSException e) {
	            e.printStackTrace();
	        } catch (Exception e) {
	            e.printStackTrace(); //eventually because of getSingleResult()
	        }
    	}
    }

    
    private ITaskWrapper getTaskWrapper(long id) {
        return entityManager.createQuery("SELECT t FROM TaskWrapper t WHERE id = :id", ITaskWrapper.class)
                .setParameter("id", id)
                .getSingleResult();
    }

    
    private void schedulerCreate(Serializable object) {
    	if (object == null) {
    		LOGGER.info("No object received");
            return;
        }
    	else{
	    	TaskWrapperDTO dto = (TaskWrapperDTO)object;
	        
	        //Set States and Persist the task
	        TaskWrapper task = new TaskWrapper();
	        task.setTaskId(dto.getTaskId());
	        task.setState(LifecycleState.ASSIGNED);
	        task.setComplexity(Complexity.UNRATED);
	
	        entityManager.persist(task);
	
	        try {
	            //Forward to a Taskforce
	
	            RateTaskWrapperDTO taskforceDTO = new RateTaskWrapperDTO(task);
	            ObjectMessage cm = session.createObjectMessage(taskforceDTO);
	            cm.setIntProperty(Constants.PROPERTY_MESSAGE_TYPE, Constants.MSG_SERVER_TO_TASKFORCE_DECIDE);
	            taskforcesProducer.send(cm);
	
	            //Reply to scheduler
	            TaskWrapperDTO schedDTO = new TaskWrapperDTO(task);
	            ObjectMessage sm = session.createObjectMessage(schedDTO);
	            sm.setIntProperty(Constants.PROPERTY_MESSAGE_TYPE, Constants.MSG_SERVER_TO_SCHEDULER_CREATED);
	            
	            schedulerProducer.send(sm);
	            
	        } catch (JMSException e) {
	            e.printStackTrace();
	        }
    	}
    }

}
